/*
 * $AIST_Release: 0.9.0 $
 * Copyright 2011 Information Technology Research Institute, National
 * Institute of Advanced Industrial Science and Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package parser

import scala.util.parsing.input.Positional
import scala.PartialFunction
import util.JavaTypes._
import Results._

/**
 * The module to walk AST.
 */
object TreeWalker {
  
  /**
   * AST node base trait.
   */
  trait Node extends Product {
    private val constructors = this.getClass.getConstructors
    require(constructors.length == 1)

    /**
     * Creates same type Node from specified child elements.
     */
    def createFromElements(elems: List[Any]): Node = {
      // type checking of "elems" is done by Constructor#newInstance().
      val copy = constructors(0).newInstance(elems.map(any2anyRef):_*)
      (this, copy) match {
        case (x: Positional, copy: Positional) => copy.setPos(x.pos)
        case (_, x) =>
      }
      return copy.asInstanceOf[Node]
    }
    
    /**
     * Gets child elemens.
     */
    def elements() = productIterator.toList
  }

  /**
   * Module to access for each Node.
   */
  object Walker {
    /**
     * Do nothing walker.
     */
    val nullWalker  = new Walker(doNothing, doNothing, List({ _ => doNothing}))

    /**
     * Basic walker.
     * This accesses List and Option.
     */
    val basicWalker = nullWalker.setFollower { 
      f => {
        case x: List[_]   => mergeList(x.map(f)).map(unit)
        case x: Option[_] => processOpt(x)(f).map(unit)
      }
    }.setFollower { f => Tuple.tupleForeach(f) }

    /**
     * Create walker to access the specified class and its subclass.
     */
    def apply(c: Class[_]) = basicWalker.setDefaultFollower(c)
  }

  /**
   * Class to access for each Node.
   */
  class Walker private(
    preprocess:  PartialFunction[Any, Result[Unit]],
    postprocess: PartialFunction[Any, Result[Unit]],
    followers:   List[(Any => Result[Unit]) => PartialFunction[Any, Result[Unit]]]) {

    /**
     * Sets partition function to call for each node before walking children.
     */
    def setPreprocess(f: PartialFunction[Any, Result[Unit]]): Walker = new Walker(f.orElse(preprocess), postprocess, followers)

    /**
     * Sets partition function to call for each node after walking children.
     */
    def setPostprocess(f: PartialFunction[Any, Result[Unit]]): Walker = new Walker(preprocess, f.orElse(postprocess), followers)

    /**
     * Sets partition function to follow child elements.
     */
    def setFollower(f: (Any => Result[Unit]) => PartialFunction[Any, Result[Unit]]): Walker = {
      new Walker(preprocess, postprocess, f::followers)
    }

    /**
     * Sets default follower to walk specified class.
     */
    def setDefaultFollower(c: Class[_]) = setFollower {
      f => { case x: Node if c.isAssignableFrom(x.getClass) => mergeList(x.elements.map(f)).map(unit) }
    }

    private lazy val follower = followers.map { _ (walk) }.reduceLeft{ (l, r) => l.orElse(r) }

    /**
     * Starts to walk.
     */
    def walk(tree: Any): Result[Unit] = {
      (preprocess(tree) ~ follower(tree) ~ postprocess(tree)).map(unit)
    }
  }

  /**
   * Module to convert the specified nodes in AST.
   */
  object Mapper {
    /**
     * Empty mapper
     */
    val nullMapper  = new Mapper(id, id, List({ _ => id}))

    /**
     * Mapper to process List and Option.
     */
    val basicMapper = nullMapper.setFollower { 
      f => {
        case x: List[_]   => mergeList(x.map(f))
        case x: Option[_] => processOpt(x)(f)
      }
    }.setFollower { f => Tuple.tupleMap(f) }

    /**
     * Creates Mapper to process the specified class and its subclass.
     */
    def apply(c: Class[_]) = basicMapper.setDefaultFollower(c)
  }

  /**
   * Class to convert the specified nodes in AST.
   */
  class Mapper private(
    preprocess:  PartialFunction[Any, Result[Any]],
    postprocess: PartialFunction[Any, Result[Any]],
    followers:   List[(Any => Result[Any]) => PartialFunction[Any, Result[Any]]]) {

    /**
     * Sets the partial function to convert Node before converting children elements.
     */
    def setPreprocess(f: PartialFunction[Any, Result[Any]]): Mapper = new Mapper(f.orElse(preprocess), postprocess, followers)

    /**
     * Sets the partial function to convert Node after converting children elements.
     */
    def setPostprocess(f: PartialFunction[Any, Result[Any]]): Mapper = new Mapper(preprocess, f.orElse(postprocess), followers)

    /**
     * Sets the partial function to convert Node in converting children elements.
     */
    def setFollower(f: (Any => Result[Any]) => PartialFunction[Any, Result[Any]]): Mapper = {
      new Mapper(preprocess, postprocess, f::followers)
    }

    /**
     * Sets default follower to convert specified class.
     */
    def setDefaultFollower(c: Class[_]) = setFollower {
      f => { case x: Node if c.isAssignableFrom(x.getClass) =>
        if (x.elements.nonEmpty) mergeList(x.elements.map(f)).map(x.createFromElements)
        else                     Success(x) 
        // Can't copy case object, and is not nessesary to copy node that has no children.
      }
    }

    private val follower = followers.map{_(this.map)}.reduceLeft { (l, r) => l.orElse(r)}

    /**
     * Starts to converta AST.
     */
    def map(tree: Any): Result[Any] = {
      preprocess(tree).flatMap { follower(_) }.flatMap{postprocess(_)}
    }
  }

  /**
   * Walks AST.
   */
  def walkTree(c: Class[_], tree: Any)(f: PartialFunction[Any, Result[Unit]]): Result[Unit] = Walker(c).setPostprocess(f).walk(tree)

  /**
   * Map AST to AST.
   */
  def mapTree(c: Class[_], tree: Any)(f: PartialFunction[Any, Result[Any]]): Result[Any] = Mapper(c).setPostprocess(f).map(tree)

  private val id: PartialFunction[Any, Result[Any]] = { case x => Success(x) }
  private val doNothing: PartialFunction[Any, Result[Unit]] = { case _ => Success(Unit) }
  private def unit[T](x: T): Unit = Unit

  private def any2anyRef(any: Any): AnyRef = {
    any match {
      case x: Boolean => new JBool(x)
      case x: Byte    => new JByte(x)
      case x: Char    => new JChar(x)
      case x: Double  => new JDouble(x)
      case x: Long    => new JLong(x)
      case x: Float   => new JFloat(x)
      case x: Int     => new JInt(x)
      case x: Short   => new JShort(x)
      case x: AnyRef  => x
    }
  }
}
